<?php
namespace DRY\Module;

/**
 * DonReY Framework 2012 :: Formdata module - Handles form preparation and post/get handling, in a simple way.
 * Doesn't attempt to build the form's HTML or manage it's layout, it instead provides strictly the data necessary for form elements ( name, default value, optionally ID )
 * Can handle $_POST or $_GET array and use the already posted values as defaults ( supporting nested names: name="group[element]" => $_POST['group']['element'] )
 *
 * @author Wavetrex <wavetrex@gmail.com>
 * @link http://www.donrey.net/Documentation/Structure/Modules/formdata.html
 * @license [GPL v3] http://www.gnu.org/licenses/gpl-3.0.html
 * @version 1.0.dev
 * @package DonReY
 * @subpackage Module.Formdata
 */
class Formdata extends base
{
	// Exception/Error codes
	const
		EXC_UNKNOWN = 0,
		Autoload_Directory = 'formdata',
		Autoload_Prefix = 'DRY\Formdata\Element',
		Autoload_Suffix = 'element'
		;

	/**
	 * Returns a Form object
	 * Enter description here ...
	 */
	public function factory_Form()
	{
		return new \DRY\Formdata\Form();
	}


}

namespace DRY\Formdata;

use DRY\Module\Formdata;

class Form
{
	/**
	 * Array of form elements
	 * ( key = element ID, used to find it in the output array ; value = DRY\Formdata\Element object )
	 * @var unknown_type
	 */
	protected $elements;

	/**
	 * Adds an element to the form, so it's data can be prepared or recognised in the Input array
	 *
	 * @param string $type What kind of element ( Not the HTML types ! ). Can be "String", "Select", "Choices", ?
	 * @param string $id Key in the array of elements
	 * @param string $name Name of the element (if differs from the $id, otherwise the $id is used)
	 * - $name can be empty string, in which case a group is necessary, and the element will be named: group[] (can be used for multiple checkboxes under the same name)
	 * @param string $group Dot.syntax location of the element ( if missing, the element is in the root of the Input array )
	 * Example (when element $name is "element"):
	 * - top.middle will use name: "top[middle][element]", and is searched in $input['top']['middle']['element']
	 *
	 * @param string|array $default Either the intial String, or the initial choices for other type of elements
	 * @param array $options Necessary to build groups of elements (like checkboxes, options for <select />, radio buttons), or other kinds of options for speciality elements
	 *
	 * @return DRY\Formdata\Element_base
	 */
	public function add_Element($element_type, $id, $name = null, $default = null, $options = array(), $group = null)
	{
		$element_class = Formdata::Autoload_Prefix.'_'.$element_type;

		if(isset($this-> elements[$id]))
			throw new \Exception("Element ID already used!"); // TODO: proper exceptions

		if(is_null($name))
			$name = $id;
		$this-> elements[$id] = new $element_class($id, $name, $default, $options, $group);
		return $this-> elements[$id];
	}

	/**
	 * Set default values of all inputs by searching their name + group inside an array of values
	 * (which might come from a previous $_POST, or the result of a query, or build customly )
	 */
	public function input($value_array)
	{
		foreach($this-> elements as $element) {
			// search $input for the element
			$value = \search_recursive_array_With_KeyMap($value_array, $element-> get_Location_Map());
			if(!is_null($value))
			$element-> input($value);
		}
	}

	/**
	 * Constructs the output data array, which could be used for example in a template system (or other uses)
	 * @return array
	 */
	public function output()
	{
		$output = array();
		foreach($this-> elements as $id=> $element) {
			$output[$id] = $element-> output();
		}
		return $output;
	}

	/**
	 * Object oriented method of accessing element's output by the element's ID
	 *
	 * @param string $element_id
	 */
	public function __get($element_id)
	{
		if(isset($this-> elements[$element_id]))
			return $this-> elements[$element_id]-> output();
	}
}

abstract class Element_base
{
	public $id;

	public $name;

	public $group;

	/**
	 * Creates a HTML Name ( group[subgroup][element] ) out of the element name and group (which is in dot.syntax)
	 * @return string
	 */
	protected function render_HTMLName()
	{
		if(!empty($this-> group)) {
			$group_fragments = explode('.', $this-> group);
			$htmlgroups = '';
			foreach($group_fragments as $idx=> $subgroup) {
				if($idx==0)
					$htmlgroups.= $subgroup;
				else
					$htmlgroups.= "[{$subgroup}]";
			}
			return $htmlgroups."[{$this-> name}]";
		} else
			return $this-> name;
	}

	protected function render_HTMLId($separator = '_')
	{
		if(!empty($this-> group)) {
			$htmlgroups = str_replace('.', $separator, $this-> group);
			return $htmlgroups."{$separator}{$this-> name}";
		} else
			return $this-> name;
	}

	public function get_Location_Map()
	{
		$map = array();
		if(!empty($this-> group)) {
			$map = explode('.', $this-> group);
			if(!empty($this-> name))
				$map[] = $this-> name;
		}
		return $map;
	}

	protected $default;

	abstract function input($value);

	protected $output_cache = null;

	abstract function output();
}

/**
 * String type form element ( input type="text", type="password", type="hidden", textarea )
 *
 * @author Wavetrex <wavetrex@gmail.com>
 * @link http://www.donrey.net/Documentation/Structure/Modules/filter.html
 * @license [GPL v3] http://www.gnu.org/licenses/gpl-3.0.html
 * @version 1.0.dev
 * @package DonReY
 * @subpackage Module.Formdata
 */
class Element_String extends Element_base
{
	public function __construct($id, $name, $default, $options, $group)
	{
		$this-> id = $id;
		$this-> name = $name;
		if($name === '' && empty($group))
			throw new \Exception("When the name is empty, a group is mandatory !");
		$this-> group = $group;

		$this-> default = $default;
	}

	public function input($value)
	{
		if(!is_scalar($value))
			throw new \Exception("Input value for String element cannot be of complex type");
		$this-> default = $value;
		$this-> output_cache = null;
	}

	/**
	 * Prepares the data of this element type for output
	 */
	public function output()
	{
		if(is_null($this-> output_cache))
			$this-> output_cache = array(
				'id'=> $this-> render_HTMLId(),
				'name'=> $this-> render_HTMLName(),
				'value'=> $this-> default
			);
		return $this-> output_cache;
	}
}

/**
 * Single choice type form element ( input type="radio", select (multiple = off) + option(s) )
 *
 * @author Wavetrex <wavetrex@gmail.com>
 * @link http://www.donrey.net/Documentation/Structure/Modules/filter.html
 * @license [GPL v3] http://www.gnu.org/licenses/gpl-3.0.html
 * @version 1.0.dev
 * @package DonReY
 * @subpackage Module.Formdata
 */
class Element_Select extends Element_base
{
	public $options;

	public function __construct($id, $name, $default, $options = array(), $group)
	{
		$this-> id = $id;
		$this-> name = $name;
		if($name === '' && empty($group))
			throw new \Exception("When the name is empty, a group is mandatory !");
		$this-> group = $group;

		$this-> default = $default;

		if(!is_array($options) || empty($options))
			throw new \Exception("Select-type form control must have at least one option");
		$this-> options = $options;
	}

	public function input($value)
	{
		if(!is_scalar($value))
			throw new \Exception("Input value for Select element cannot be of complex type");
		$this-> default = $value;
		$this-> output_cache = null;
	}

	/**
	 * Prepares the data of this element type for output
	 */
	public function output()
	{
		if(is_null($this-> output_cache)) {
			$this-> output_cache = array(
				'id'=> $this-> render_HTMLId(),
				'name'=> $this-> render_HTMLName()
			);
			$this-> output_cache['options'] = array();
			foreach($this-> options as $value=> $display) {
				$option = array(
					'value'=> $value,
					'display'=> $display
				);
				if($value == $this-> default)
					$option['active'] = true;
				$this-> output_cache['options'][] = $option;
			}
		}
		return $this-> output_cache;
	}
}

/**
 * Multiple choice type form element ( input type="checkbox", select (multiple = on) + option(s) )
 *
 * @author Wavetrex <wavetrex@gmail.com>
 * @link http://www.donrey.net/Documentation/Structure/Modules/filter.html
 * @license [GPL v3] http://www.gnu.org/licenses/gpl-3.0.html
 * @version 1.0.dev
 * @package DonReY
 * @subpackage Module.Formdata
 */
class Element_Choices extends Element_base
{
	public $options;

	public function __construct($id, $name, $default, $options = array(), $group)
	{
		$this-> id = $id;
		$this-> name = $name;
		if($name === '' && empty($group))
			throw new \Exception("When the name is empty, a group is mandatory !");
		$this-> group = $group;

		$this-> default = $default;

		if(!is_array($options) || empty($options))
			throw new \Exception("Choices-type form control must have at least one option");
		$this-> options = $options;
	}

	public function input($value)
	{
		if(!is_array($value))
			throw new \Exception("Input value for Choices element must an array");
		$this-> default = $value;
		$this-> output_cache = null;
	}

	/**
	 * Prepares the data of this element type for output
	 */
	public function output()
	{
		if(is_null($this-> output_cache)) {
			$html_name = $this-> render_HTMLName();
			$this-> output_cache = array(
				'id'=> $this-> render_HTMLId(),
				'name'=> $html_name."[]"
			);
			$this-> output_cache['options'] = array();
			foreach($this-> options as $value=> $display) {
				$option = array(
					'value'=> $value,
					'display'=> $display,
					'name'=> "{$html_name}[{$value}]"
				);
				if(in_array($value, $this-> default))
					$option['active'] = true;
				$this-> output_cache['options'][] = $option;
			}
		}
		return $this-> output_cache;
	}

}